package hu.mapro.jpa.model.server;

import hu.mapro.jpa.FetchGraph;
import hu.mapro.jpa.JpaUtils;
import hu.mapro.jpa.ManyToOneFetch;
import hu.mapro.jpa.ManyToOneProperty;
import hu.mapro.jpa.OneToManyFetch;
import hu.mapro.jpa.OneToManyFetchType;
import hu.mapro.jpa.OneToManyProperty;
import hu.mapro.jpa.model.domain.client.AutoBeans.FetchPlan;
import hu.mapro.jpa.model.domain.client.AutoBeans.FetchProperty;
import hu.mapro.model.impl.Cardinality;
import hu.mapro.model.meta.EntityType;
import hu.mapro.model.meta.Field;
import hu.mapro.model.meta.MetaUtils;
import hu.mapro.model.meta.PluralField;

import java.util.Collection;
import java.util.List;

import javax.persistence.EntityManager;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

public class JpaGwtLister<R, I> {
	
	Function<EntityType<?>, Class<?>> persistentClass;
	
	public JpaGwtLister(
			Function<EntityType<?>, Class<?>> persistentClass
	) {
		this.persistentClass = persistentClass;
	}
	
	public List<R> list(			
			EntityManager entityManager,
			EntityType<I> entityType,
			FetchPlan fetchPlan
	) {
		return JpaUtils.list(entityManager, new GwtFetchGraph<R, I>(fetchPlan, entityType));
	}

	public static <C, I> List<C> list(
			EntityManager entityManager,
			EntityType<I> entityType,
			Function<EntityType<?>, Class<?>> persistentClass,
			FetchPlan fetchPlan
	) {
		return new JpaGwtLister<C, I>(persistentClass).list(entityManager, entityType, fetchPlan);
	}

	final class GwtFetchGraph<T, E> implements FetchGraph<T> {

		FetchPlan fetchPlan;
		EntityType<E> entityType;
		
		public GwtFetchGraph(FetchPlan fetchPlan, EntityType<E> entityType) {
			super();
			this.fetchPlan = fetchPlan;
			this.entityType = entityType;
		}

		Collection<ManyToOneFetch<T, ?>> manyToOneFetches = Lists.newArrayList();
		Collection<OneToManyFetch<T, ?>> oneToManyFetches = Lists.newArrayList();

		void init() {
			for (FetchProperty fp : fetchPlan.getProperties()) {
				Field<? super E, ?> field = MetaUtils.findField(entityType,
						fp.getPropertyName());

				// TODO check field, security
				
				if (field.getCardinality() == Cardinality.SCALAR) {
					addManyToOneFetch(field, fp.getFetchPlan());
				} else {
					addOneToManyFetch((PluralField<? super E, ?>) field, fp.getFetchPlan());
				}
			}
		}

		private <P, C> void addManyToOneFetch(final Field<? super E, P> field, final FetchPlan fetchPlan) {
			manyToOneFetches.add(new ManyToOneFetch<T, P>() {

				@Override
				public ManyToOneProperty<T, P> getProperty() {
					return new ManyToOneProperty<T, P>() {
						@Override
						public String getName() {
							return field.getName();
						}
					};
				}

				@SuppressWarnings("unchecked")
				@Override
				public FetchGraph<P> getFetchGraph() {
					return new GwtFetchGraph<P, C>(
							fetchPlan,
							(EntityType<C>) field.getValueType()
					);
				}
			});
		}

		private <P, C> void addOneToManyFetch(final PluralField<? super E, P> field, final FetchPlan fetchPlan) {
			oneToManyFetches.add(new OneToManyFetch<T, P>() {

				@Override
				public OneToManyProperty<T, P> getProperty() {
					return new OneToManyProperty<T, P>() {

						@Override
						public String getName() {
							return field.getName();
						}

						@Override
						public ManyToOneProperty<P, T> getInverse() {
							return new ManyToOneProperty<P, T>() {
								@Override
								public String getName() {
									return field.getInverseField().getName();
								}
							};
						}
					};
				}

				@SuppressWarnings("unchecked")
				@Override
				public FetchGraph<P> getFetchGraph() {
					return new GwtFetchGraph<P, C>(
							fetchPlan,
							(EntityType<C>) field.getValueType()
					);
				}

				@Override
				public OneToManyFetchType getFetchType() {
					return OneToManyFetchType.PARAMS;
				}

				@Override
				public boolean isManyToOneDirect() {
					return false;
				}
			});
		}

		@SuppressWarnings("unchecked")
		@Override
		public Class<T> getEntityClass() {
			return (Class<T>) persistentClass.apply(entityType);
		}

		@Override
		public Collection<? extends ManyToOneFetch<T, ?>> getManyToOne() {
			return manyToOneFetches;
		}

		@Override
		public Collection<? extends OneToManyFetch<T, ?>> getOneToMany() {
			return oneToManyFetches;
		}
	}

}
